// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef SRC_READER_SPIRV_CONSTRUCT_H_
#define SRC_READER_SPIRV_CONSTRUCT_H_

#include <memory>
#include <sstream>
#include <string>
#include <vector>

namespace tint {
namespace reader {
namespace spirv {

/// A structured control flow construct, consisting of a set of basic blocks.
/// A construct is a span of blocks in the computed block order,
/// and will appear contiguously in the WGSL source.
///
/// SPIR-V (2.11 Structured Control Flow) defines:
///   - loop construct
///   - continue construct
///   - selection construct
/// We also define a "function construct" consisting of all the basic blocks in
/// the function.
///
/// The first block in a construct (by computed block order) is called a
/// "header". For the constructs defined by SPIR-V, the header block is the
/// basic block containing the merge instruction.  The header for the function
/// construct is the entry block of the function.
///
/// Given two constructs A and B, we say "A encloses B" if B is a subset of A,
/// i.e. if every basic block in B is also in A.  Note that a construct encloses
/// itself.
///
/// In a valid SPIR-V module, constructs will nest, meaning given
/// constructs A and B, either A encloses B, or B encloses A, or
/// or they are disjoint (have no basic blocks in commont).
///
/// A loop in a high level language translates into either:
//
///  - a single-block loop, where the loop header branches back to itself.
///     In this case this single-block loop consists only of the *continue
///     construct*.  There is no "loop construct" for this case.
//
///  - a multi-block loop, where the loop back-edge is different from the loop
///     header.
///     This case has both a non-empty loop construct containing at least the
///     loop header, and a non-empty continue construct, containing at least the
///     back-edge block.
///
/// We care about two kinds of selection constructs:
///
///  - if-selection: where the header block ends in OpBranchConditional
///
///  - switch-selection: where the header block ends in OpSwitch
///
struct Construct {
  /// Enumeration for the kinds of structured constructs.
  enum Kind {
    /// The whole function.
    kFunction,
    /// A SPIR-V selection construct, header basic block ending in
    /// OpBrancConditional.
    kIfSelection,
    /// A SPIR-V selection construct, header basic block ending in OpSwitch.
    kSwitchSelection,
    /// A SPIR-V loop construct.
    kLoop,
    /// A SPIR-V continue construct.
    kContinue,
  };

  /// Constructor
  /// @param the_parent parent construct
  /// @param the_depth construct nesting depth
  /// @param the_kind construct kind
  /// @param the_begin_id block id of the first block in the construct
  /// @param the_end_id block id of the first block after the construct, or 0
  /// @param the_begin_pos block order position of the_begin_id
  /// @param the_end_pos block order position of the_end_id or a too-large value
  /// @param the_scope_end_pos block position of the first block past the end of
  /// the WGSL scope
  Construct(const Construct* the_parent,
            int the_depth,
            Kind the_kind,
            uint32_t the_begin_id,
            uint32_t the_end_id,
            uint32_t the_begin_pos,
            uint32_t the_end_pos,
            uint32_t the_scope_end_pos);

  /// @param pos a block position
  /// @returns true if the given block position is inside this construct.
  bool ContainsPos(uint32_t pos) const {
    return begin_pos <= pos && pos < end_pos;
  }
  /// Returns true if the given block position is inside the WGSL scope
  /// corresponding to this construct. A loop construct's WGSL scope encloses
  /// the associated continue construct. Otherwise the WGSL scope extent is the
  /// same as the block extent.
  /// @param pos a block position
  /// @returns true if the given block position is inside the WGSL scope.
  bool ScopeContainsPos(uint32_t pos) const {
    return begin_pos <= pos && pos < scope_end_pos;
  }

  /// The nearest enclosing construct other than itself, or nullptr if
  /// this construct represents the entire function.
  const Construct* const parent = nullptr;
  /// The nearest enclosing loop construct, if one exists.  Points to `this`
  /// when this is a loop construct.
  const Construct* const enclosing_loop = nullptr;
  /// The nearest enclosing continue construct, if one exists.  Points to
  /// `this` when this is a contnue construct.
  const Construct* const enclosing_continue = nullptr;
  /// The nearest enclosing loop construct or continue construct or
  /// switch-selection construct, if one exists. The signficance is
  /// that a high level language "break" will branch to the merge block
  /// of such an enclosing construct. Points to `this` when this is
  /// a loop construct, a continue construct, or a switch-selection construct.
  const Construct* const enclosing_loop_or_continue_or_switch = nullptr;

  /// Control flow nesting depth. The entry block is at nesting depth 0.
  const int depth = 0;
  /// The construct kind
  const Kind kind = kFunction;
  /// The id of the first block in this structure.
  const uint32_t begin_id = 0;
  /// 0 for kFunction, or the id of the block immediately after this construct
  /// in the computed block order.
  const uint32_t end_id = 0;
  /// The position of block #begin_id in the computed block order.
  const uint32_t begin_pos = 0;
  /// The position of block #end_id in the block order, or the number of
  /// block order elements if #end_id is 0.
  const uint32_t end_pos = 0;
  /// The position of the first block after the WGSL scope corresponding to
  /// this construct.
  const uint32_t scope_end_pos = 0;
};

using ConstructList = std::vector<std::unique_ptr<Construct>>;

/// Converts a construct kind to a string.
/// @param kind the construct kind to convert
/// @returns the string representation
inline std::string ToString(Construct::Kind kind) {
  switch (kind) {
    case Construct::kFunction:
      return "Function";
    case Construct::kIfSelection:
      return "IfSelection";
    case Construct::kSwitchSelection:
      return "SwitchSelection";
    case Construct::kLoop:
      return "Loop";
    case Construct::kContinue:
      return "Continue";
  }
  return "NONE";
}

/// Converts a construct into a short summary string.
/// @param c the construct, which can be null
/// @returns a short summary string
inline std::string ToStringBrief(const Construct* c) {
  if (c) {
    std::stringstream ss;
    ss << ToString(c->kind) << "@" << c->begin_id;
    return ss.str();
  }
  return "null";
}

/// Emits a construct to a stream.
/// @param o the stream
/// @param c the structured construct
/// @returns the stream
inline std::ostream& operator<<(std::ostream& o, const Construct& c) {
  o << "Construct{ " << ToString(c.kind) << " [" << c.begin_pos << ","
    << c.end_pos << ")"
    << " begin_id:" << c.begin_id << " end_id:" << c.end_id
    << " depth:" << c.depth;

  o << " parent:" << ToStringBrief(c.parent);

  if (c.scope_end_pos != c.end_pos) {
    o << " scope:[" << c.begin_pos << "," << c.scope_end_pos << ")";
  }

  if (c.enclosing_loop) {
    o << " in-l:" << ToStringBrief(c.enclosing_loop);
  }

  if (c.enclosing_continue) {
    o << " in-c:" << ToStringBrief(c.enclosing_continue);
  }

  if ((c.enclosing_loop_or_continue_or_switch != c.enclosing_loop) &&
      (c.enclosing_loop_or_continue_or_switch != c.enclosing_continue)) {
    o << " in-c-l-s:" << ToStringBrief(c.enclosing_loop_or_continue_or_switch);
  }

  o << " }";
  return o;
}

/// Emits a construct to a stream.
/// @param o the stream
/// @param c the structured construct
/// @returns the stream
inline std::ostream& operator<<(std::ostream& o,
                                const std::unique_ptr<Construct>& c) {
  return o << *(c.get());
}

/// Converts a construct to a string.
/// @param c the construct
/// @returns the string representation
inline std::string ToString(const Construct& c) {
  std::stringstream ss;
  ss << c;
  return ss.str();
}

/// Converts a construct to a string.
/// @param c the construct
/// @returns the string representation
inline std::string ToString(const Construct* c) {
  return c ? ToString(*c) : ToStringBrief(c);
}

/// Converts a unique pointer to a construct to a string.
/// @param c the construct
/// @returns the string representation
inline std::string ToString(const std::unique_ptr<Construct>& c) {
  return ToString(*(c.get()));
}

/// Emits a construct list to a stream.
/// @param o the stream
/// @param cl the construct list
/// @returns the stream
inline std::ostream& operator<<(std::ostream& o, const ConstructList& cl) {
  o << "ConstructList{\n";
  for (const auto& c : cl) {
    o << "  " << c << "\n";
  }
  o << "}";
  return o;
}

/// Converts a construct list to a string.
/// @param cl the construct list
/// @returns the string representation
inline std::string ToString(const ConstructList& cl) {
  std::stringstream ss;
  ss << cl;
  return ss.str();
}

}  // namespace spirv
}  // namespace reader
}  // namespace tint

#endif  // SRC_READER_SPIRV_CONSTRUCT_H_
